<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
  <title>赛马牌 · 桌面适配（手动发牌）</title>
  <style>
    :root{
      --card-w:90px;
      --card-h:125px;
      --row-h:125px; /* 每行高度 = 一张牌高度 */
      --steps:5;
      --cols-lanes:4;
    }
    *{box-sizing:border-box}
    html,body{height:100%;margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,'Segoe UI','Noto Sans CJK SC',"Microsoft YaHei",sans-serif;background:linear-gradient(180deg,#071128,#07203a);color:#e9f3ff}
    .app{max-width:1200px;margin:18px auto;padding:18px}
    header{display:flex;justify-content:space-between;align-items:center;margin-bottom:14px}
    h1{margin:0;font-size:20px}
    .meta{color:#9fb4c8;font-size:13px}

    .game{display:flex;gap:20px}
    .board{flex:1;background:rgba(255,255,255,0.02);border-radius:10px;padding:12px}

    .board-grid{display:flex;flex-direction:column;gap:8px}
    .board-row{display:grid;grid-template-columns:repeat(var(--cols-lanes),1fr) 140px;align-items:center;gap:10px;height:var(--row-h)}
    .cell{background:linear-gradient(180deg,rgba(255,255,255,0.01),rgba(255,255,255,0.02));border-radius:8px;border:1px solid rgba(255,255,255,0.03);display:flex;align-items:center;justify-content:center;padding:6px;position:relative}

    .horse-img{width:var(--card-w);height:var(--card-h);border-radius:8px;box-shadow:0 6px 18px rgba(0,0,0,.45);transition:transform 0.5s cubic-bezier(0.25, 0.1, 0.25, 1);}
    .obs-wrap{width:var(--card-w);height:var(--card-h);perspective:900px}
    .card-inner{width:100%;height:100%;position:relative;transform-style:preserve-3d;transition:transform .6s cubic-bezier(0.25, 0.1, 0.25, 1);}
    .card-inner.flipped{transform:rotateY(180deg)}
    .card-face{position:absolute;inset:0;border-radius:8px;backface-visibility:hidden;overflow:hidden}
    .card-back{background:#0b1224;border:2px solid rgba(255,255,255,0.03)}
    .card-front{transform:rotateY(180deg);border:2px solid rgba(255,255,255,0.04)}

    .side{width:320px;display:flex;flex-direction:column;gap:12px}
    .deck-top{display:flex;align-items:center;gap:12px;position:relative}
    .deck-top img{width:var(--card-w);height:var(--card-h)}
    .controls{display:flex;gap:8px;flex-direction:column;margin-top:12px;}
    button{padding:10px;border-radius:8px;border:none;background:#1f6f8b;color:white;font-weight:700;cursor:pointer;transition:all 0.2s ease;}
    button:hover{background:#2585a8;}
    button.ghost{background:transparent;border:1px solid rgba(255,255,255,0.06)}
    button.ghost:hover{background:rgba(255,255,255,0.05);}
    button:disabled{background:#555;cursor:not-allowed;opacity:0.7;}

    .footer-note{font-size:13px;color:#9fb4c8;margin-top:12px;}

    /* 胜利提示框 */
    .winner-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:999;opacity:0;pointer-events:none;transition:opacity 0.3s ease;}
    .winner-modal.active{opacity:1;pointer-events:auto;}
    .winner-box{background:#fff;border-radius:12px;padding:24px 36px;text-align:center;color:#222;box-shadow:0 8px 28px rgba(0,0,0,.45);animation:zoomIn .4s ease}
    .winner-box h2{margin:0 0 12px;font-size:24px;color:#1f6f8b}
    .winner-box button{margin-top:16px;background:#1f6f8b;color:#fff;padding:10px 18px;font-size:15px}
    @keyframes zoomIn{from{transform:scale(.7);opacity:0}to{transform:scale(1);opacity:1}}

    @media (max-width:900px){
      :root{--card-w:72px;--card-h:100px;--row-h:100px}
      .game{flex-direction:column}
      .side{width:100%}
      .board-row{grid-template-columns:repeat(var(--cols-lanes),1fr) 100px}
    }
  </style>
</head>
<body>
  <div class="app">
    <header>
      <div>
        <h1>赛马牌 · 桌面适配（手动发牌）</h1>
        <div class="meta">每行高度=一张牌；马起始行在第一障碍下方；超过第五障碍算胜利；发牌有动画；胜利提示更美观。</div>
      </div>
      <div class="meta">共有 5 个障碍 + 起点与终点</div>
    </header>

    <div class="game">
      <div class="board">
        <div id="boardGrid" class="board-grid"></div>
      </div>

      <aside class="side">
        <div class="deck-top">
          <div id="deckTop">
            <div class="obs-wrap">
              <div id="currentCard" class="card-inner">
                <div class="card-face card-back"><img src="https://deckofcardsapi.com/static/img/back.png" width="100%" height="100%"/></div>
                <div class="card-face card-front"><img id="currentCardImg" src="" width="100%" height="100%"/></div>
              </div>
            </div>
          </div>
          <div>
            <div>剩余牌：<strong id="remain">0</strong></div>
            <div style="margin-top:6px;color:#9fb4c8;font-size:13px">点击“抽一张”翻牌</div>
          </div>
        </div>

        <div class="controls">
          <button id="btnDraw">抽一张（手动）</button>
          <button id="btnReset" class="ghost">重新开始</button>
        </div>

        <div class="footer-note">障碍从下到上排列，所有马严格越过某障碍时翻开，触发对应马退后。</div>
      </aside>
    </div>
  </div>

  <div id="winnerModal" class="winner-modal">
    <div class="winner-box">
      <h2 id="winnerTitle">🏆 获胜！</h2>
      <button onclick="document.getElementById('winnerModal').classList.remove('active')">关闭</button>
    </div>
  </div>

<script>
(function(){
  const TOTAL_STEPS = 5;
  const ROWS = TOTAL_STEPS + 2;
  const FINISH_LINE = TOTAL_STEPS; // 终点线位置
  const CARD_BACK = 'https://deckofcardsapi.com/static/img/back.png';
  const ranks = ['A','2','3','4','5','6','7','8','9','0','J','Q','K'];
  const suits = ['S','H','D','C'];
  const HORSES_DEF = [
    { suit:'S', code:'0S', label:'♠ 10' },
    { suit:'H', code:'JH', label:'♥ J' },
    { suit:'D', code:'QD', label:'♦ Q' },
    { suit:'C', code:'KC', label:'♣ K' }
  ];

  let deck = [], obstacles = [], horses = [], drawIndex = 0;
  let isDrawing = false; // 防止重复点击

  const boardGrid = document.getElementById('boardGrid');
  const remainEl = document.getElementById('remain');
  const currentCard = document.getElementById('currentCard');
  const currentCardImg = document.getElementById('currentCardImg');
  const winnerModal = document.getElementById('winnerModal');
  const winnerTitle = document.getElementById('winnerTitle');
  const btnDraw = document.getElementById('btnDraw');

  function shuffle(arr){for(let i=arr.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[arr[i],arr[j]]=[arr[j],arr[i]];}return arr;}
  function makeFullDeck(){const a=[];for(const r of ranks){for(const s of suits){a.push(r+s);}}return shuffle(a);}
  function imgURL(code){return `https://deckofcardsapi.com/static/img/${code}.png`;}

  function buildGrid(){
    boardGrid.innerHTML='';
    for(let row=ROWS-1;row>=0;row--){
      const rowEl=document.createElement('div');
      rowEl.className='board-row';
      rowEl.dataset.row=row;

      // 为终点线添加特殊样式
      if(row === FINISH_LINE) {
        rowEl.style.background = 'rgba(31, 111, 139, 0.1)';
        rowEl.style.border = '1px dashed rgba(31, 111, 139, 0.3)';
      }

      for(let lane=0;lane<4;lane++){
        const cell=document.createElement('div');
        cell.className='cell';
        cell.id=`cell-${row}-${lane}`;
        rowEl.appendChild(cell);
      }
      const obsCell=document.createElement('div');
      obsCell.className='cell';
      obsCell.id=`cell-${row}-obs`;
      rowEl.appendChild(obsCell);
      boardGrid.appendChild(rowEl);
    }
  }

  function render(skipCardReset = false){
    // 清空所有单元格
    for(let r=0;r<ROWS;r++){
      for(let lane=0;lane<4;lane++){
        const cell = document.getElementById(`cell-${r}-${lane}`);
        if(cell) cell.innerHTML='';
      }
      const obsCell = document.getElementById(`cell-${r}-obs`);
      if(obsCell) obsCell.innerHTML='';
    }

    // 渲染障碍牌
    for(let s=1;s<=TOTAL_STEPS;s++){
      const ob=obstacles.find(o=>o.step===s);
      const cell=document.getElementById(`cell-${s}-obs`);
      if(!cell || !ob) continue;
      const wrap=document.createElement('div');
      wrap.className='obs-wrap';
      const inner=document.createElement('div');
      inner.className='card-inner';
      if(ob.revealed) inner.classList.add('flipped');
      inner.innerHTML=`<div class="card-face card-back"><img src="${CARD_BACK}" width="100%" height="100%"/></div><div class="card-face card-front"><img src="${imgURL(ob.code)}" width="100%" height="100%"/></div>`;
      wrap.appendChild(inner);
      cell.appendChild(wrap);
    }

    // 渲染马
    horses.forEach((h,idx)=>{
      const cell=document.getElementById(`cell-${h.pos}-${idx}`);
      if(!cell) return;

      // 检查是否已创建该马的图片元素，避免重复创建
      let horseImg = cell.querySelector('.horse-img');
      if(!horseImg) {
        horseImg = document.createElement('img');
        horseImg.className='horse-img';
        cell.appendChild(horseImg);
      }

      horseImg.src=imgURL(h.code);

      // 如果是新移动，添加平滑动画
      if(h.lastMovedAt === drawIndex) {
        horseImg.animate([
          { transform: 'translateY(30px)', opacity: 0.6 },
          { transform: 'translateY(0)', opacity: 1 }
        ], {
          duration: 600,
          easing: 'cubic-bezier(0.25, 0.1, 0.25, 1)'
        });
      }
    });

    // 更新剩余牌数
    remainEl.textContent=deck.length;

    // 重置当前卡牌状态（除非指定跳过）
    if(!skipCardReset){
      currentCard.classList.remove('flipped');
      currentCardImg.src = '';
    }
  }

  function resetGame(){
    isDrawing = false;
    drawIndex=0;
    const full=makeFullDeck();
    const obsCodes=full.splice(0,TOTAL_STEPS);
    obstacles=[];
    for(let i=0;i<TOTAL_STEPS;i++){
      obstacles.push({
        code:obsCodes[i],
        suit:obsCodes[i].slice(-1),
        revealed:false,
        step:i+1
      });
    }
    deck=full.slice();
    horses=HORSES_DEF.map(h=>({
      suit:h.suit,
      code:h.code,
      pos:0,
      lastMovedAt:null
    }));
    buildGrid();
    render();
    btnDraw.disabled = false;
    winnerModal.classList.remove('active');
  }

  function drawOne(){
    // 防止重复点击和动画冲突
    if(isDrawing || deck.length === 0) return;
    isDrawing = true;
    btnDraw.disabled = true;

    // 先确保卡牌是背面朝上
    currentCard.classList.remove('flipped');

    setTimeout(() => {
      drawIndex++;
      const code=deck.shift();

      // 设置当前卡牌图片
      currentCardImg.src=imgURL(code);

      // 翻转动画显示牌面
      setTimeout(() => {
        currentCard.classList.add('flipped');

        // 移动对应花色的马
        const suit=code.slice(-1);
        const horse=horses.find(h=>h.suit===suit);
        if(horse){
          // 记录当前位置用于动画
          const oldPos = horse.pos;
          horse.pos=Math.min(ROWS-1,horse.pos+1);
          horse.lastMovedAt=drawIndex;
        }

        // 渲染但不重置卡牌状态
        render(true);

        // 检查障碍和胜利条件
        setTimeout(()=>{
          checkObstacles();
          render(true);

          setTimeout(()=>{
            // 明确检查是否有马越过终点线
            const hasWinner = checkWin();
            if(!hasWinner && deck.length === 0) {
              endByDeckEmpty();
            }
            isDrawing = false;
            btnDraw.disabled = deck.length === 0 || hasWinner;
          }, 200);
        }, 400); // 等待翻牌动画完成
      }, 100);
    }, 150);
  }

  function checkObstacles(){
    for(const ob of obstacles){
      if(ob.revealed) continue;
      const step=ob.step;
      const allPassed=horses.every(h=>h.pos>step);
      if(allPassed){
        ob.revealed=true;
        const affected=horses.find(h=>h.suit===ob.suit);
        if(affected){
          const oldPos = affected.pos;
          affected.pos=Math.max(0,affected.pos-1);
          affected.lastMovedAt=drawIndex;

          // 添加退后动画效果
          const cellId = `cell-${affected.pos}-${horses.indexOf(affected)}`;
          const cell = document.getElementById(cellId);
          if(cell && cell.firstChild){
            cell.firstChild.animate([
              { transform: 'translateX(-15px)' },
              { transform: 'translateX(15px)' },
              { transform: 'translateX(0)' }
            ], {
              duration: 600,
              easing: 'cubic-bezier(0.25, 0.1, 0.25, 1)'
            });
          }
        }
        const el=document.getElementById(`cell-${step}-obs`);
        if(el) el.animate([
          {transform:'scale(1)'},
          {transform:'scale(1.08)'},
          {transform:'scale(1)'}
        ],{
          duration: 300,
          easing: 'cubic-bezier(0.25, 0.1, 0.25, 1)'
        });
      }
    }
  }

  function checkWin(){
    // 明确判断是否超过终点线
    const finishers=horses.filter(h=>h.pos > FINISH_LINE);
    if(finishers.length>0){
      finishers.sort((a,b)=>(a.lastMovedAt||Infinity)-(b.lastMovedAt||Infinity));
      announceWinner(finishers[0]);
      return true;
    }
    return false;
  }

  function endByDeckEmpty(){
    const maxPos=Math.max(...horses.map(h=>h.pos));
    const leaders=horses.filter(h=>h.pos===maxPos);
    leaders.sort((a,b)=>(a.lastMovedAt||Infinity)-(b.lastMovedAt||Infinity));
    announceWinner(leaders[0]);
  }

  function announceWinner(horse){
    winnerTitle.textContent=`🏆 ${horse.label} 获胜！`;
    winnerModal.classList.add('active');
    btnDraw.disabled = true;
  }

  document.getElementById('btnDraw').addEventListener('click',()=>{drawOne();});
  document.getElementById('btnReset').addEventListener('click',()=>{resetGame();});

  // 初始加载时设置牌背图片
  document.querySelector('#deckTop .card-back img').src = CARD_BACK;

  resetGame();
})();
</script>
</body>
</html> 